package com.daniel.authytest.server;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ConnectException;
import java.net.URL;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.codec.binary.Base64;

import com.daniel.authytest.shared.Constants;

import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import net.sf.json.JSONSerializer;

public class GuitHubSearcher
{
    /**
     * list of filetypes to be ignored in lowercase
     */
    public final static String[] IGNORE_FILETYPES = { ".mp3", ".ogg", ".png", ".gif", ".jpg", ".svg", ".avi", ".swf" };

    /**
     * This method search the given string in the github url repository 
     * @param gitHubUrl
     * @param findTextParam
     * @param caseSensitive
     * @return Json with the files details where the string was found
     * @throws Exception if the is an empty parameter, if the give url is incorrect, if the repository does not exist, if the is problem communicating with GitHub
     */
    public JSONObject searchOnGitHug( String gitHubUrl, String findTextParam, boolean caseSensitive ) throws Exception
    {
        String findText = findTextParam;
        String url = gitHubUrl.toLowerCase( );
        String patttern = "^http(s?)://(www.)?github.com/([a-zA-Z0-9._-]+)/([a-zA-Z0-9._-]+)(/?)(.)*$";
        Pattern p = Pattern.compile( patttern );
        Matcher m = p.matcher( url );

        if( gitHubUrl == null || gitHubUrl.trim( ).equals( "" ) || findTextParam == null || findTextParam.trim( ).equals( "" ) )
            throw new Exception( "<strong>GitHub URL</strong> and <strong>Find text</strong> cannot be empty" );

        if( !m.find( ) )
            throw new Exception( "<strong>Please check the URL</strong> (ie:https://github.com/myUser/myRepo )" );

        if( !caseSensitive )
            findText = findText.toLowerCase( );

        String gitUser = url.replaceAll( patttern, "$3" );
        String gitRepo = url.replaceAll( patttern, "$4" );

        JSONObject result = new JSONObject( );
        result.put( Constants.PROJECT, gitRepo );
        result.put( Constants.USER, gitUser );
        result.put( Constants.HITS_COUNT, "0" );
        result.put( Constants.FILES, new JSONArray( ) );
        result.put( Constants.FILES_COUNT, "0" );
        result.put( Constants.FIND_TEXT, findText );

        searchOnFolder( "", gitUser, gitRepo, result, caseSensitive, findText, 0 );
        return result;
    }

    /**
     * Search recursively the given string in all files and folders in the given repository, accumulated in the recursiveJson 
     * @param path
     * @param user
     * @param repo
     * @param recursiveJson
     * @param caseSensitive
     * @param findText
     * @throws Exception
     */
    private void searchOnFolder( String path, String user, String repo, JSONObject recursiveJson, boolean caseSensitive, String findText, int deepth ) throws Exception
    {
        String response = getUrl( Constants.GITHUB_API_SERVICE + "/" + user + "/" + repo + "/contents/" + path );
        JSONArray responseJson;
        try
        {
            responseJson = ( JSONArray )JSONSerializer.toJSON( response );
        }
        catch( Exception e )// GitHub dindt return an array, somehitng went wrog => there is a message
        {
            JSONObject responseJsonMessage = ( JSONObject )JSONSerializer.toJSON( response );
            throw new Exception( "<strong>GitHub Message:</strong> " + responseJsonMessage.getString( Constants.GH_MESS ) );
        }

        for( Object object : responseJson )
        {
            JSONObject childJson = ( JSONObject )object;
            if( childJson.getString( Constants.GH_FILE_TYPE ).equals( "dir" ) )
                searchOnFolder( ( path.equals( "" ) ? "" : path + "/" ) + childJson.getString( "path" ), user, repo, recursiveJson, caseSensitive, findText, deepth + 1 );
            else
                searchOnFile( ( path.equals( "" ) ? "" : path + "/" ) + childJson.getString( "path" ), user, repo, recursiveJson, caseSensitive, findText );
        }
    }
    /**
     * Search if the file located in 'path' contains the given String, if the the string exists the file details are saved in 'recursiveJson'
     * @param path
     * @param user
     * @param repo
     * @param recursiveJson
     * @param caseSensitive
     * @param findText
     * @throws IOException
     * @throws ConnectException
     */
    private void searchOnFile( String path, String user, String repo, JSONObject recursiveJson, boolean caseSensitive, String findText ) throws IOException, ConnectException
    {
        if( path.contains( "." ) )
        {
            String fileType = path.substring( path.lastIndexOf( "." ), path.length( ) ).toLowerCase( );
            if( Arrays.asList( IGNORE_FILETYPES ).contains( fileType ) )
            {
                System.out.println( "ignorado: " + path );
                return;
            }
        }

        String response = getUrl( Constants.GITHUB_API_SERVICE + "/" + user + "/" + repo + "/contents/" + path );

        JSONObject responseJson = ( JSONObject )JSONSerializer.toJSON( response );
        if( !responseJson.containsKey( Constants.GH_ENCONDING ) || !responseJson.getString( Constants.GH_ENCONDING ).equals( "base64" ) )
        {
            // Never gets here
            System.err.println( "no se puede decodificar el archivo: " + path );
            return;
        }

        String content = new String( Base64.decodeBase64( responseJson.getString( "content" ).getBytes( ) ) );

        if( !caseSensitive )
            content = content.toLowerCase( );

        if( content.contains( findText ) )
        {
            Pattern pattern = Pattern.compile( ".{0,60}" + findText + ".{0,60}" );
            Matcher matcher = pattern.matcher( content );

            JSONArray hitsArray = new JSONArray( );
            while( matcher.find( ) )
            {
                String hitLine = matcher.group( );

                int indexOfFindeText = hitLine.indexOf( findText );
                if( hitLine.length( ) - ( indexOfFindeText + findText.length( ) ) == 60 )
                    hitLine = hitLine + " ...";

                if( indexOfFindeText == 60 )
                    hitLine = "... " + hitLine;
                hitsArray.add( hitLine );
            }

            int hits = content.split( findText ).length - 1;
            JSONObject file = new JSONObject( );
            file.put( Constants.HITS_COUNT, hits + "" );
            file.put( Constants.FILE_NAME, path );
            file.put( Constants.FILE_URL, ( String ) ( ( JSONObject )responseJson.get( Constants.GH_LINKS ) ).get( Constants.GH_HTML ) );
            file.put( Constants.FILE_LINES, hitsArray );

            int filesCount = Integer.parseInt( recursiveJson.getString( Constants.FILES_COUNT ) ) + 1;
            recursiveJson.discard( Constants.FILES_COUNT );
            recursiveJson.put( Constants.FILES_COUNT, filesCount + "" );

            int hitsCount = Integer.parseInt( recursiveJson.getString( Constants.HITS_COUNT ) ) + hits;
            recursiveJson.discard( Constants.HITS_COUNT );
            recursiveJson.put( Constants.HITS_COUNT, hitsCount + "" );

            recursiveJson.accumulate( Constants.FILES, file );
        }

    }

    /**
     * Get the response of a given URL
     * @param urlString
     * @return
     * @throws IOException
     * @throws ConnectException
     */
    private String getUrl( String urlString ) throws IOException, ConnectException
    {
        System.out.println( urlString );
        URL url = new URL( urlString );

        try
        {
            url.openConnection( ).connect( );
        }
        catch( Exception e )
        {
            System.err.println( "reintentando conexion A" );
            try
            {
                url.openConnection( ).connect( );// retry connection
            }
            catch( Exception e1 )
            {
                throw new ConnectException( "Could not connect to GitHub, try again later" );
            }

        }

        InputStream is = null;
        try
        {
            is = url.openStream( );
        }
        catch( Exception e )
        {
            System.err.println( "reintentando conexion B" );
            try
            {
                is = url.openStream( );// retry connection
            }
            catch( Exception e1 )
            {
                throw new ConnectException( "Could not connect to GitHub, try again later" );
            }
        }

        InputStreamReader isr = new InputStreamReader( is );
        BufferedReader in = new BufferedReader( isr );
        String response = "";

        String line;
        while( ( line = in.readLine( ) ) != null )
        {
            response += line;
        }
        in.close( );

        return response;
    }

    /**
     * Test Method
     * @param args
     * @throws Exception
     */
    @Deprecated
    public static void main( String[] args ) throws Exception
    {
        GuitHubSearcher t = new GuitHubSearcher( );
        String gitHubUrl = "https://github.com/jsantell/dancer.js";
        String findText = "gemspec";
        boolean caseSensitive = false;
        System.out.println( t.searchOnGitHug( gitHubUrl, findText, caseSensitive ) );

    }

}
